/**
 * Licensed under the Apache License Version 2.0.
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
package net.interknit.knitter.cg.model;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;

import net.interknit.knitter.cg.CGMain;
import net.interknit.knitter.cg.cfg.CGConfig;
import net.interknit.knitter.cg.util.CGLogger;
import net.interknit.knitter.cg.util.ReflectionHelper;
import net.interknit.knitter.cg.util.Utils;

/**
 * JavaType represents a Java type in a more structured way which can be used for better parsing of type
 * signatures, method access and generic presentation. It maintains a Class object which represents the
 * underlying type along with Type objects that represent the underlying type's generic types in the form of
 * non-parameterized presentation. It also maintains the array information of the underlying type. For
 * example, if a Type represents java.util.Map&lt;String, ? extends Number&gt;, then the JavaType will
 * maintain java.util.Map as the type Class, java.lang.String and java.lang.Number as generic types, and array
 * dimension equal to 0. If Type represented an array (generic or otherwise), then the array dimension will maintain
 * the dimensions of this array (0, 1, 2 and so on).
 *
 */
public class JavaType
{
	/**
	 * Creates the JavaType object from the given Type object. This method decides if the input type argument
	 * is a seed type (i.e., entry point type) or a non-seed type (i.e., subsequent type).
	 * <br><br>
	 * <b>Note:</b> validate() must be called on the returned (newly created) JavaType object to check if
	 * it represents a valid type or not.  Invalid JavaTypes should be discarded.
	 *
	 * @param type Type argument from which the new JavaType is to be created
	 * @return Newly created JavaType object
	 */
	public static JavaType create(Type type, Type... genericTypeArgs)
	{
		//
		// create the JavaType from the type
		//
		JavaType inst = new JavaType(type, genericTypeArgs);

		//
		// if JavaTypeMap is empty, then assume that
		// the type is a seedType (i.e., the entry point type).
		//
		inst.seedType = JavaTypeMap.isEmpty();

		//
		// will be used as the key for JavaTypeMap.
		//
		String key = inst.getJavaTypeId();

		//
		// instead of overwriting the value for
		// a pre-existing key, skip it.
		//
		if(! JavaTypeMap.containsKey(key))
		{
			JavaTypeMap.put(key, inst);
		}

		return inst;
	}

	/**
	 * Is this JavaType the seed type?  The "service" class, which will be the first JavaType built by the
	 * {@link JavaTypeScanner} will be considered the seed type.  The seed type will be exposed as a service
	 * provider class.  All subsequent types will be considered non-seed types.
	 */
	private boolean seedType = false;

	/**
	 * The simplified representation of the rawClass. typeClass does not carry the generic information, and is
	 * the Java representation of this JavaType, except for the array and primitive information. If rawClass
	 * represents an array type (of any dimension), typeClass will represent the root-most component type of that
	 * array. For example, if rawClass represents '<code>[Ljava.lang.String;</code>' (i.e.,
	 * <code>java.lang.String[]</code>), then typeClass will represent '<code>java.lang.String</code>' class.
	 * Likewise, if rawClass represents '<code>int</code>' class, then typeClass will represent '
	 * <code>java.lang.Integer</code>' class.
	 */
	private final Class<?> typeClass;

	/**
	 * List of Type objects, where each object represents the explicit type generics of this JavaType in the
	 * correct order. This list may be empty for non-generic types, or for types that do not explicitly define
	 * the type parameters (i.e., raw types).<br/>
	 * <br/>
	 * For example, if this JavaType represents
	 * <code>java.util.Map&lt;java.lang.String, java.util.Date&gt;</code>, then genericTypes list will contain
	 * <code>java.lang.String</code> at index 0 and <code>java.util.Date</code> at index 1 of the list.  However,
	 * if the same Map tyep is defined as a raw type (i.e., Map map = ...), then the genericTypes for that typeClass
	 * would be empty.
	 */
	private List<Type> genericTypes;

	/**
	 * List of Type objects, where each object represents the implicit type generics of this JavaType in the correct
	 * order.  This list will contain the built-int type parameters of the typeClass even if they are not explicitly
	 * define.  For example, if a Class Foo&lt;K, V&gt; is defined as a raw type (i.e., Foo foo = ...), then this
	 * list will contain the type parameters for K and V in that order.  derviedGenericTypes is only applicable for
	 * a Type that is of type java.lang.Class.
	 */
	private List<Type> derivedGenericTypes;

	/**
	 * Represents the dimension of the array type represented by this JavaType. If JavaType
	 * represents a non-array type, then, arrayDimensions will be 0. If JavaType represents an array type of 1
	 * dimension, then arrayDimensions will be 1, and so on.
	 */
	private int arrayDimensions;

	/**
	 * List of JavaMethod objects representing the methods of the type represented by this JavaType. This
	 * variable acts as a cache of the built list, as scanning methods into JavaMethod objects is an expensive
	 * operation that will return the same result every time it is executed. To improve the performance, the
	 * list will only be built once, and will be cached for this object until this object is garbage
	 * collected.
	 */
	private List<JavaMethod> javaMethodsCache;

	/**
	 * List of JavaType objects representing the explicit type generics of the type represented by this JavaType. This
	 * variable acts as a cache of the built list, as scanning type generics into JavaType objects is an
	 * expensive operation that will return the same result every time it is executed. To improve the
	 * performance, the list will only be built once, and will be cached for this object until this object is
	 * garbage collected.
	 */
	private List<JavaType> genericJavaTypesCache;

	/**
	 * List of JavaType objects representing the implicit type generics of the type represented by this JavaType. This
	 * variable acts as a cache of the built list, as scanning type generics into JavaType objects is an
	 * expensive operation that will return the same result every time it is executed. To improve the
	 * performance, the list will only be built once, and will be cached for this object until this object is
	 * garbage collected.
	 */
	private List<JavaType> derivedGenericJavaTypesCache;

	/**
	 * The JavaType representation of the underlying typeClass. This is only constructed the first time, and
	 * then remains available for this object until this object is garbage collected. <br/>
	 * <br/>
	 * componentJavaTypeCache is only applicable when this JavaType represents an array type.
	 */
	private JavaType componentJavaTypeCache;

	/**
	 * The JavaType representation of the component type's raw version, if this JavaType is an array. Raw form
	 * means a representation without any type generic information. This is only constructed the first time,
	 * and then remains available for this object until this object is garbage collected. For example, if this
	 * JavaType represents 'Class&lt;? extends InputStream&gt;[]', then rawComponentJavaTypeCache will
	 * represent 'Class[]'.<br/>
	 * <br/>
	 * rawComponentJavaTypeCache is only applicable when this JavaType represents an array type.
	 */
	private JavaType rawComponentJavaTypeCache;

	/**
	 * The JavaType representation of the concrete type of the underlying typeClass (if typeClass is an
	 * interface or an abstract class). This is only constructed the first time, and then remains available
	 * for this object until this object is garbage collected.
	 */
	private JavaType concreteJavaTypeCache;

	/**
	 * The JavaType representation of this JavaType without the array information. This is only constructed
	 * the first time, and then remains available for this object until this object is garbage collected. The
	 * subtle difference between this value and componentJavaTypeCache is that noArrayJavaTypeCache will
	 * represent the same JavaType as this JavaType, except for the array information. Whereas,
	 * componentJavaTypeCache represents the component type of this JavaType (only if this JavaType is an
	 * array) with or without the array information. <br/>
	 * <br/>
	 * For example, if this JavaType represents 'java.lang.String[][]', then the componentJavaTypeCache will
	 * represent 'java.lang.String[]' (1 dimension instead of 2). Whereas, the noArrayJavaTypeCache will
	 * represent 'java.lang.String' (no array information). Note that, if this JavaType represents
	 * 'java.lang.String[]', then the componentJavaTypeCache and noArrayJavaTypeCache will be identical.
	 */
	private JavaType noArrayJavaTypeCache;

	/**
	 * Package name of the underlying Java Type.  If underlying Java Type is <code>com.foo.Calculator</code>,
	 * then the value of javaTypePackageName will be <code>com.foo</code>
	 */
	private String javaTypePackageName;

	/**
	 * Count of number of accessor (i.e., getters and setters) methods.  accessorCount is only calculated for
	 * non-seed types.  The value of accessorCount is the raw number of the total of number of getter, setter
	 * and query methods.  For example, if a class has the folloing getters and setters, the accessCount value
	 * would be 6.  Note that the accessorCount will always be an even number, as types with valid pairs of
	 * getter/setter/query methods is considered a valid type.  The getter/setter/query method rules are same
	 * as the ones published by JavaBean specifications.
	 * <pre><code>
	 *   public String getUserName() { ... }
	 *   setUserName(String userName) { ... }
	 *   public SecretKey getSecretKey() { ... }
	 *   public void setSecretKey(SecretKey key) { ... }
	 *   public boolean isDisabled() { ... }
	 *   public void setDisabled(boolean disabled) { ... }
	 * </code></pre>
	 */
	private int accessorCount = 0;

	/**
	 * Is this type referenced by any other generated types?  If not, then it will be discarded at the time
	 * of code generation.
	 */
	private boolean referenced = true;

	/**
	 * The inspector to inspect this JavaType
	 */
	private JavaTypeInspector inspector;

	/**
	 * Flag to ensure that we do not build this JavaType multiple times.
	 * See 'build()' method for details
	 */
	private boolean built = false;

	/**
	 * Private constructor to create a new JavaType.  It is only called by the JavaType.create() method.
	 *
	 * @param type Java Type for which a new JavaType will be created
	 */
	private JavaType(Type type, Type... genericTypeArgs)
	{
		// find the raw class (the non-generic version if type is a generic type)
		// rawClass could be an array.
		Class<?> rawClass = ReflectionHelper.getUnderlyingClass(type);

		//
		// if rawClass is an array, use it's component type to find the underlying class
		// (mainly to convert primitive types to their boxed counterparts).
		//
		if(rawClass.isArray())
		{
			rawClass = ReflectionHelper.getUnderlyingClass(rawClass.getComponentType());
		}

		this.typeClass = ReflectionHelper.getRootComponentClass(rawClass);

		if(genericTypeArgs != null && genericTypeArgs.length > 0)
		{
			this.genericTypes = new ArrayList<Type>();
			this.derivedGenericTypes = new ArrayList<Type>();

			for(Type genericTypeArg : genericTypeArgs)
			{
				this.genericTypes.add(genericTypeArg);
				this.derivedGenericTypes.add(genericTypeArg);
			}
		}
		else
		{
			this.genericTypes = ReflectionHelper.getGenericTypes(type);
			this.derivedGenericTypes = ReflectionHelper.getDerivedGenericTypes(type);
		}

		this.arrayDimensions = ReflectionHelper.getArrayDimensions(type);

		createJavaTypePackageName();
	}

	/**
	 * Create a temporary JavaType, with empty properties except for the typeClass.
	 */
	private JavaType(Class<?> typeClass)
	{
		this.typeClass = typeClass;
		this.genericTypes = null;
		this.derivedGenericTypes = ReflectionHelper.getDerivedGenericTypes(typeClass);
		this.arrayDimensions = 0;

		createJavaTypePackageName();
	}

	/**
	 * Sets javaTypePackageName of this class by concatenating the base package name from Configuration
	 * with the package name of this.typeClass.  For example, if the basePackageName is <code>xyz</code> and
	 * the package name of typeClass is <code>com.foo.common</code>, then the javaTypePackageName is set to
	 * <code>xyz.com.foo.common</code>. The default value (if not specified in cgconfig.xml) for
	 * base package name is <code>generated</code>.  Therefore, in the above example, if the base package name
	 * is not specified, the default javaTypePackageName would be <code>generated.com.foo.common</code>.
	 */
	private void createJavaTypePackageName()
	{
		this.javaTypePackageName = CGMain.getInstance().getCGConfig().getBasePackageName() + "." +
				ReflectionHelper.getPackageName(this.typeClass);

		if(javaTypePackageName.endsWith("."))
			javaTypePackageName = javaTypePackageName.substring(0, javaTypePackageName.length() - 1);
	}

	/**
	 * Construct a List of JavaType objects, each representing the generic Type for the type represented by
	 * this JavaType object. In other words, this method will iterate over all objects of the list returned by
	 * getGenericTypes(), and create a corresponding JavaType, the collection of which - in the same order -
	 * is returned back.
	 */
	void buildGenericJavaTypes()
	{
		if(genericJavaTypesCache == null)
		{
			genericJavaTypesCache = new ArrayList<JavaType>();
			for(Type t : genericTypes) genericJavaTypesCache.add(JavaType.create(t));
		}

		if(derivedGenericJavaTypesCache == null)
		{
			derivedGenericJavaTypesCache = new ArrayList<JavaType>();
			for(Type t : derivedGenericTypes) derivedGenericJavaTypesCache.add(JavaType.create(t));
		}
	}

	/**
	 * Construct a List of JavaMethod objects, each representing a method of this JavaType. If this JavaType
	 * represents an Array type, an empty list will be returned as arrays are considered primitive constructs
	 * and not types.
	 */
	void buildJavaMethods()
	{
		if(javaMethodsCache == null)
		{
			//
			// don't build method list for array types.  arrays are not really *types*
			//
			if(arrayDimensions > 0)
			{
				javaMethodsCache = new ArrayList<JavaMethod>();
			}
			else
			{
				javaMethodsCache = getMemberMethods();

				if(! seedType)
				{
					for(JavaMethod jm : javaMethodsCache)
					{
						if(jm.isAccessor())
							accessorCount++;
					}
				}
			}
		}
	}

	/**
	 * Build the componentJavaTypeCache and rawComponentJavaTypeCache for this JavaType.
	 * This method is only applicable to array types.
	 */
	void buildComponentJavaType()
	{
		if(getInspector().isArrayType())
		{
			if(componentJavaTypeCache == null)
			{
				componentJavaTypeCache = new JavaType(getTypeClass());
				componentJavaTypeCache.genericTypes = this.genericTypes;
				componentJavaTypeCache.arrayDimensions = this.arrayDimensions - 1;

				JavaTypeMap.put(
						componentJavaTypeCache.getJavaTypeId(),
						componentJavaTypeCache);
			}

			if(rawComponentJavaTypeCache == null)
			{
				rawComponentJavaTypeCache = new JavaType(getTypeClass());
				rawComponentJavaTypeCache.genericTypes = new ArrayList<Type>();
				rawComponentJavaTypeCache.arrayDimensions = this.arrayDimensions - 1;

				JavaTypeMap.put(
						rawComponentJavaTypeCache.getJavaTypeId(),
						rawComponentJavaTypeCache);
			}
		}
	}

	/**
	 * Build the conreteJavaTypeCache for this JavaType.  If the underlying typeClass is already a concrete
	 * type, it will be set as the concrete JavaType.  Otherwise, the method will try to guess the best match
	 * for the underlying JavaType and use that as the concrete class.
	 */
	void buildConcreteJavaType()
	{
		if(concreteJavaTypeCache == null)
		{
			// if typs is a non-concrete, non-array and non-enum type
			if(! getInspector().isConcrete() && ! getInspector().isArrayType() && ! getInspector().isEnumType())
			{
				Class<?> concrete = getTypeClass();
				if(getInspector().isBasicType()) concrete = Double.class;
				else if(getInspector().isSetType()) concrete = HashSet.class;
				else if(getInspector().isListType() || getInspector().isCollectionType()) concrete = ArrayList.class;
				else if(getInspector().isMapType()) concrete = HashMap.class;
				else concrete = getTypeClass();

				concreteJavaTypeCache = new JavaType(concrete);
				concreteJavaTypeCache.genericTypes = this.genericTypes;
				concreteJavaTypeCache.derivedGenericTypes = this.derivedGenericTypes;
				concreteJavaTypeCache.arrayDimensions = this.arrayDimensions;

				JavaTypeMap.put(
						concreteJavaTypeCache.getJavaTypeId(),
						concreteJavaTypeCache);

				concreteJavaTypeCache.referenced = false;
			}
			else concreteJavaTypeCache = this;
		}
	}

	/**
	 * Build noArrayJavaTypeCache for this JavaType.
	 */
	void buildNoArrayJavaTypeCache()
	{
		if(noArrayJavaTypeCache == null)
		{
			if(getInspector().isArrayType())
			{
				noArrayJavaTypeCache = new JavaType(getTypeClass());
				noArrayJavaTypeCache.genericTypes = this.genericTypes;

				JavaTypeMap.put(
						noArrayJavaTypeCache.getJavaTypeId(),
						noArrayJavaTypeCache);
			}
			else noArrayJavaTypeCache = this;
		}
	}

	/**
	 * Is this JavaType already built?
	 *
	 * @return true, if this JavaType is already built;  false otherwise.
	 */
	boolean isBuilt()
	{
		return built;
	}

	/**
	 * Set the built field of this JavaType
	 *
	 * @param built
	 *            set to true if build() method is already called on this JavaType. Default value of built is
	 *            false.
	 */
	void setBuilt(boolean built)
	{
		this.built = built;
	}

	/**
	 * Is this JavaType a seed type?
	 *
	 * @return true if this JavaType is a seed type; false otherwise.
	 */
	public boolean isSeedType()
	{
		return seedType;
	}

	/**
	 * Is this JavaType referenced elsewhere?
	 *
	 * @return true if this JavaType is referenced elsewhere; false otherwise.
	 */
	public boolean isReferenced()
	{
		return referenced;
	}

	/**
	 * Get the JavaTypeInspector for this JavaType.
	 *
	 * @return JavaTypeInspector object for this JavaType.
	 */
	public JavaTypeInspector getInspector()
	{
		return inspector != null ? inspector : (inspector = new JavaTypeInspector(this));
	}

	/**
	 * Get the package name of the underlying typeClass. If the underlying typeClass is package-less, return
	 * an empty string.
	 *
	 * @return Value of the package name of the underlying typeClass, or empty string if underlying typeClass
	 *         is in the default package
	 */
	public String getJavaTypePackageName()
	{
		return javaTypePackageName;
	}

	/**
	 * The simplified representation of the rawClass. typeClass does not carry the generic information, and is
	 * the Java representation of this JavaType, except for the array and primitive information. If rawClass
	 * represents an array type (of any dimension), typeClass will represent the root-most component type of that
	 * array. For example, if rawClass represents '<code>[Ljava.lang.String;</code>' (i.e.,
	 * <code>java.lang.String[]</code>), then typeClass will represent '<code>java.lang.String</code>' class.
	 * Likewise, if rawClass represents '<code>int</code>' class, then typeClass will represent '
	 * <code>java.lang.Integer</code>' class.
	 *
	 * @return the typeClass The value of the simplified representation of rawClass object
	 */
	public Class<?> getTypeClass()
	{
		return typeClass;
	}

	/**
	 * Get the JavaType representation of the underlying typeClass.
	 *
	 * @return JavaType representation of the underlying typeClass.
	 */
	public JavaType getComponentJavaType()
	{
		return componentJavaTypeCache;
	}

	/**
	 * The JavaType representation of the component type's raw version, if this JavaType is an array. Raw form
	 * means a representation without any type generic information. This is only constructed the first time,
	 * and then remains available for this object until this object is garbage collected. For example, if this
	 * JavaType represents 'Class&lt;? extends InputStream&gt;[]', then rawComponentJavaTypeCache will
	 * represent 'Class[]'.<br/>
	 * <br/>
	 * rawComponentJavaTypeCache is only applicable when this JavaType represents an array type.
	 *
	 * @return The JavaType representation of this JavaType without the generic type parameter information
	 */
	public JavaType getRawComponentJavaType()
	{
		return rawComponentJavaTypeCache;
	}

	/**
	 * Get the JavaType representation of the concrete type of the underlying typeClass (if typeClass is an
	 * interface or an abstract class). If typeClass is not an interface or abstract class (i.e., is already a
	 * concrete class), or if it is an Enum, an array or a primitive type (or an equivalent boxed type), then
	 * 'this' is returned. A concrete type dos not necessarily mean an instantiable type. It only means that
	 * it represents a type that is not an interface or an abstract class.
	 *
	 * @return The JavaType representation of the concrete type of the underlying typeClass.
	 */
	public JavaType getConcreteJavaType()
	{
		return concreteJavaTypeCache;
	}

	/**
	 * Get the JavaType representation of this JavaType without the array information. This is only constructed
	 * the first time, and then remains available for this object until this object is garbage collected. The
	 * subtle difference between this value and componentJavaTypeCache is that noArrayJavaTypeCache will
	 * represent the same JavaType as this JavaType, except for the array information. Whereas,
	 * componentJavaTypeCache represents the component type of this JavaType (only if this JavaType is an
	 * array) with or without the array information. <br/>
	 * <br/>
	 * For example, if this JavaType represents 'java.lang.String[][]', then the componentJavaTypeCache will
	 * represent 'java.lang.String[]' (1 dimension instead of 2). Whereas, the noArrayJavaTypeCache will
	 * represent 'java.lang.String' (no array information). Note that, if this JavaType represents
	 * 'java.lang.String[]', then the componentJavaTypeCache and noArrayJavaTypeCache will be identical.
	 *
	 * @return The JavaType representation of this JavaType without the array information.
	 */
	public JavaType getNoArrayJavaType()
	{
		return noArrayJavaTypeCache;
	}

	/**
	 * List of Type objects, where each object represents the type generics of this JavaType in the correct
	 * order. For example, if this JavaType represents
	 * <code>java.util.Map&lt;java.lang.String, java.util.Date&gt;</code>, then genericTypes list will contain
	 * <code>java.lang.String</code> at index 0 and <code>java.util.Date</code> at index 1 of the list.
	 *
	 * @return the genericTypes The list of Type objects representing the type generics of the type
	 *         represented by this JavaType
	 */
	public  List<Type> getGenericTypes()
	{
		return genericTypes;
	}

	/**
	 * Represents the dimension of the array type represented by this JavaType. If JavaType represents a
	 * non-array type, then, arrayDimensions will be 0. If JavaType represents an array type of 1 dimension,
	 * then arrayDimensions will be 1, and so on.
	 *
	 * @return the arrayDimensions Dimensions of the type represented by JavaType, or 0 if JavaType represents
	 *         non-array type
	 */
	public int getArrayDimensions()
	{
		return arrayDimensions;
	}

	/**
	 * Return the cached List of JavaType objects, each representing the explicit generic Type for the type
	 * represented by this JavaType object. buildGenericJavaTypes() must be called before this method call,
	 * otherwise, a null value will be returned. JavaTypeScanner will call buildGenericJavaTypes() after all
	 * JavaTypes are created.
	 *
	 * @return List of JavaType objects representing the generic types of this JavaType
	 */
	public List<JavaType> getGenericJavaTypes()
	{
		return genericJavaTypesCache;
	}

	/**
	 * Return the cached List of JavaType objects, each representing the implicit generic Type for the type
	 * represented by this JavaType object. buildGenericJavaTypes() must be called before this method call,
	 * otherwise, a null value will be returned. JavaTypeScanner will call buildGenericJavaTypes() after all
	 * JavaTypes are created.
	 *
	 * @return List of JavaType objects representing the generic types of this JavaType
	 */
	public List<JavaType> getDerivedGenericJavaTypes()
	{
		return derivedGenericJavaTypesCache;
	}

	/**
	 * Return the cached List of JavaMethod objects.
	 * buildJavaMethods() must be called before this method call, otherwise, a null value will be returned.
	 * JavaTypeScanner will call buildJavaMethods() after all JavaTypes are created.
	 *
	 * @return List of JavaMethod objects for this JavaType
	 */
	public List<JavaMethod> getJavaMethods()
	{
		return javaMethodsCache;
	}

	/**
	 * Get the total number of getters, setters and query methods.  The returned number is always an even
	 * number as the accessor methods are always pairs. Seed type do not have any accessor methods.
	 *
	 * @return Total number of getters, setters and query methods made available by the underlying typeClass.
	 */
	public int getAccessorCount()
	{
		return accessorCount;
	}

	/**
	 * Get the total number of non-accessor methods (or business functions). Only seed types have function
	 * methods.
	 *
	 * @return Total number of business function methods made available by the underlying typeClass
	 */
	public int getFunctionCount()
	{
		return javaMethodsCache.size() - accessorCount;
	}

	/**
	 * Iterate over the type parameters of the underlying typeClass (if any) and
	 * match each type argument with the given Type. If a match is found, return
	 * Type argument of this JavaType from the same index location. A null Type
	 * is returned if the typeClass has no type parameters or if a matching Type
	 * argument is not found.
	 *
	 * @param type
	 *            Type for which the matching generic JavaType is to be found
	 *
	 * @return Type representing Type argument of the this JavaType which
	 *         matches the input type
	 */
	public Type getMatchingTypeArgument(Type type)
	{
		Class<?> forClass = getTypeClass();

		int index = 0;
		TypeVariable<?>[] tvArr = forClass.getTypeParameters();

		for(int i = 0; i < tvArr.length; i++)
		{
			TypeVariable<?> tvar = tvArr[i];
			if(tvar.toString().equals(type.toString()))
			{
				index = i;
				break;
			}
		}

		if(getGenericTypes().size() > index)
			return getGenericTypes().get(index);

		Type genericSuperType = forClass.getGenericSuperclass();

		if(genericSuperType instanceof ParameterizedType)
		{
			Class<?> superClass = forClass.getSuperclass();

			tvArr = superClass.getTypeParameters();

			if(tvArr != null && tvArr.length > 0)
			{
				List<Type> genTypes = ReflectionHelper.getGenericTypes(genericSuperType);

				index = 0;
				for(int i = 0; i < tvArr.length; i++)
				{
					TypeVariable<?> tvar = tvArr[i];

					if(tvar.toString().equals(type.toString()))
					{
						index = i;
						break;
					}
				}

				return genTypes.get(index);
			}
		}

		return null;
	}

	@Override
	public boolean equals(Object obj)
	{
		boolean equal = false;
		if(obj instanceof JavaType)
		{
			JavaType other = (JavaType) obj;

			if(
					getTypeClass().equals(other.getTypeClass()) &&
					getArrayDimensions() == other.getArrayDimensions() &&
					getGenericTypes().size() == other.getGenericTypes().size())
			{
				for(int i = 0; i < getGenericTypes().size(); i++)
					equal = getGenericTypes().get(i).equals(other.getGenericTypes().get(i));
			}
		}

		return equal;
	}

	/**
	 * Return a String representing the Java declaration of this JavaType. The returned value can be used to
	 * define a variable of this JavaType. The returned declaration will include the array notations ('[]')
	 * depending upon the array dimensions, if this JavaType represents an array.
	 *
	 * @return String representation of the Java declaration for this JavaType.
	 */
	public String getJavaTypeDeclaration()
	{
		StringBuilder sb = new StringBuilder();
		sb.append(ReflectionHelper.getClassName(getTypeClass()));

		buildGenericJavaTypes();

		if(! getGenericJavaTypes().isEmpty())
		{
			sb.append("<");
			boolean first = true;
			for(JavaType jt : getGenericJavaTypes())
			{
				if(! first) sb.append(", ");
				sb.append(jt.getJavaTypeDeclaration());
				first = false;
			}
			sb.append(">");
		}

		sb.append(Utils.chain("[]", getArrayDimensions()));

		return sb.toString();
	}

	/**
	 * Return a String representing a value that can uniquely identify this JavaType. No two different
	 * JavaTypes can have the same JavaTypeId.
	 *
	 * @return Unique Id of this JavaType.
	 */
	public String getJavaTypeId()
	{
		return getJavaTypeId(CGMain.getInstance().useQualifiedTypeNames());
	}

	public String getJavaTypeId(boolean addPackageName)
	{
		StringBuilder sb = new StringBuilder();

		sb.append(ReflectionHelper.getClassName(getTypeClass(), true));

		buildGenericJavaTypes();

		if(! getGenericJavaTypes().isEmpty())
		{
			sb.append("<");
			boolean first = true;
			for(JavaType jt : getGenericJavaTypes())
			{
				if(! first) sb.append(", ");
				sb.append(jt.getJavaTypeId());
				first = false;
			}
			sb.append(">");
		}

		sb.append(Utils.chain("[]", getArrayDimensions()));

		String id = (addPackageName ? (javaTypePackageName + ".") : "") +
				ReflectionHelper.createUniqueId(sb.toString());

		return id;
	}

	@Override
	public String toString()
	{
		return getJavaTypeDeclaration();
	}

	/**
	 * Find and construct a list of JavaMethod objects for the public methods defined by the given Class.
	 * Depending upon the values of includeAcccessor and includeNonAccessor parameters, the returned list
	 * would contain accessor (getter/setter) methods and/or non-accessor methods. This method returns only
	 * public, non-abstract methods belonging to this Class (declared by this Class or by any of its super
	 * classes or super interfaces).
	 *
	 * @param clazz
	 *            Class object for which the methods are sought
	 * @return List of JavaMethod objects, or an empty List if clazz has no matching methods
	 * @see JavaAccessorMethod
	 */
	private List<JavaMethod> getMemberMethods()
	{
		List<JavaMethod> methods = new ArrayList<JavaMethod>();

		CGConfig cfg = CGMain.getInstance().getCGConfig();

		Method[] methodArr = getTypeClass().getMethods();
		for(Method method : methodArr)
		{
			CGLogger.progressLog("Parsing " + method.toGenericString());

			//
			// a defect in Java reflection APIs was identified, where in certain cases the
			// result of Class.getMethods() did not have accurate generic signatures of methods.
			// the following statement re-fetches the specific method based on its name and parameter
			// types to rectify this defect
			//
			Method m = ReflectionHelper.getMethod(getTypeClass(), method.getName(), method.getParameterTypes());

			//
			// m would only be null if Class.getMethods() and Class.getMethod() have
			// discrepancies. hope that this is not the case. but if m is null, ignore
			// it, and continue onto the next method.
			//
			if(m == null) continue;

			// if m is non-public, is static, or is a bridge method, ignore it.
			//
			if(!
					Modifier.isPublic(m.getModifiers()) ||
					Modifier.isStatic(m.getModifiers()) ||
					m.isBridge())
			{
				CGLogger.progressLog("\t" + "Ignoring the method as one of these types: " +
						"non-public, static or bridge");

				continue;
			}

			Class<?> declaringClass = m.getDeclaringClass();

			if(declaringClass.equals(Object.class))
			{
				CGLogger.progressLog("\t" + "Ignoring the method as it is declared in java.lang.Object.class");

				continue;
			}

			String className = ReflectionHelper.getDisplayClassName(getTypeClass());
			String declaringClassName = ReflectionHelper.getDisplayClassName(declaringClass);

			//
			// if getTypeClass() is not the declaring class of the method and getTypeClass()
			// is not in the included parent classes in the configuration then skip this method
			//
			if(
					seedType &&
					! declaringClass.equals(getTypeClass()) &&
					! cfg.includeParentClass(className, declaringClassName))
			{
				CGLogger.progressLog("\t" + "Ignoring the method as it is not declared in " +
						declaringClassName + " or one of it included super classes");

				continue;
			}

			//
			// if method is specified in the configuration, and if it is explicitly excluded
			// skip it. if it not specified or not excluded, scan it.
			//
			if(
					seedType &&
					cfg.excludeMethod(className, m.getName(), ReflectionHelper.getDisplayClassNames(m.getParameterTypes())))
			{
				CGLogger.progressLog("\t" + "Ignoring the method as it is explicitly excluded");

				continue;
			}

			JavaMethod javaMethod = new JavaMethod(this, m);

			//
			// if method is not a valid method for parsing, ignore it
			//
			if(! javaMethod.valid())
			{
				CGLogger.progressLog("\t" + "Ignoring the method as it is not a valid method");

				continue;
			}

			//
			// if this is not the seed type, and if the method is not an accessor method,
			// skip this method and continue on to the next
			//
			if(! seedType && ! javaMethod.isAccessor())
			{
				CGLogger.progressLog("\t" + "Ignoring the method as it is not an accessor method");

				continue;
			}

			//
			// if javaMethod is either a getter or query method, attempt to
			// find the corresponding setter method.  if setter method is
			// found, add it to the methods list (if not added previously).
			//
			if(javaMethod.isGetter() || javaMethod.isQueryMethod())
			{
				Method setterMethod = ReflectionHelper.getMethod(
						typeClass,
						Utils.deriveSetterName(Utils.derivePropertyName(m.getName())),
						m.getReturnType());

				if(setterMethod != null)
				{
					JavaMethod setter = new JavaMethod(this, setterMethod);

					if(! setter.isAccessor())
					{
						CGLogger.progressLog("\t" + "Ignoring the method as the corresponding setter method " +
								"does not qualify as an accessor method");

						continue;
					}

					if(! methods.contains(setter))
					{
						setter.buildTypes();

						methods.add(setter);
					}
				}
				else
				{
					CGLogger.progressLog("\t" + "Ignoring the getter method as the corresponding setter " +
							"method does not exist.");

					continue;
				}
			}
			else if(javaMethod.isSetter())
			{
				CGLogger.progressLog("\t" + "Ignoring the method as it is a setter method. Only" +
						"getter methods are parsed to create accessor pairs");

				continue;
			}

			//
			// if the method is not already scanned (probably as a superclass method)
			// then add it to the listing.  otherwise, continue onto the next
			//
			if(! methods.contains(javaMethod))
			{
				javaMethod.buildTypes();
				methods.add(javaMethod);
			}
			else
			{
				CGLogger.progressLog("\t" + "Ignoring the method as it was already parsed");

				continue;
			}
		}

		return methods;
	}

	public String status()
	{
		StringBuilder status = new StringBuilder();

		status.append("JavaType Declaration: " + getJavaTypeDeclaration()).append("\n");
		status.append("\t").append("isConcreteType?             : " + getInspector().isConcrete()).append("\n");
		status.append("\t").append("isVoidType?                 : " + getInspector().isVoidType()).append("\n");
		status.append("\t").append("isAnyType?                  : " + getInspector().isAnyType()).append("\n");
		status.append("\t").append("isPrimitiveType?            : " + getInspector().isPrimitiveType()).append("\n");
		status.append("\t").append("isBasicType?                : " + getInspector().isBasicType()).append("\n");
		status.append("\t").append("isBigIntegerType?           : " + getInspector().isBigIntegerType()).append("\n");
		status.append("\t").append("isBigDecimalType?           : " + getInspector().isBigIntegerType()).append("\n");
		status.append("\t").append("isFileType?                 : " + getInspector().isFileType()).append("\n");
		status.append("\t").append("isInputStreamType?          : " + getInspector().isInputStreamType()).append("\n");
		status.append("\t").append("isReaderType?               : " + getInspector().isReaderType()).append("\n");
		status.append("\t").append("isDateTimeType?             : " + getInspector().isDateTimeType()).append("\n");
		status.append("\t").append("isEnumType?                 : " + getInspector().isEnumType()).append("\n");
		status.append("\t").append("isArrayType?                : " + getInspector().isArrayType()).append("\n");
		status.append("\t").append("isSetType?                  : " + getInspector().isSetType()).append("\n");
		status.append("\t").append("isListType?                 : " + getInspector().isListType()).append("\n");
		status.append("\t").append("isCollectionType?           : " + getInspector().isCollectionType()).append("\n");
		status.append("\t").append("isMapType?                  : " + getInspector().isMapType()).append("\n");
		status.append("\t").append("isSeedType?                 : " + isSeedType()).append("\n");
		status.append("\t").append("isCustomType?               : " + getInspector().isCustomType()).append("\n");
		status.append("\t").append("built?                      : " + built).append("\n");
		status.append("\t").append("javaMethodsCache            : " + (javaMethodsCache != null ? javaMethodsCache.size() : "null")).append("(size)").append("\n");
		status.append("\t").append("genericJavaTypesCache       : " + (genericJavaTypesCache != null ? genericJavaTypesCache.size() : "null")).append("(size)").append("\n");
		status.append("\t").append("derivedGenericJavaTypesCache: " + (derivedGenericJavaTypesCache != null ? derivedGenericJavaTypesCache.size() : "null")).append("(size)").append("\n");
		status.append("\t").append("componentJavaTypeCache      : " + (componentJavaTypeCache != null ? componentJavaTypeCache : "null")).append("\n");
		status.append("\t").append("rawComponentJavaTypeCache   : " + (rawComponentJavaTypeCache != null ? rawComponentJavaTypeCache : "null")).append("\n");
		status.append("\t").append("concreteJavaTypeCache       : " + (concreteJavaTypeCache != null ? concreteJavaTypeCache : "null")).append("\n");
		status.append("\t").append("noArrayJavaTypeCache        : " + (noArrayJavaTypeCache != null ? noArrayJavaTypeCache : "null")).append("\n");

		return status.toString();
	}
}
